package plugins;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Classe gérant le chargement et la validation des plugins
 * @author Florian Minin & Khémon Beh
 *
 */
public class ChargeurPlugin {

    private List<String> files;
    private ArrayList classMediaPlugins;
    private Object classSearchPlugins;
    private static final Class[] PARAMETERS = new Class[]{URL.class};

    /**
     * Constructeur par défaut
     *
     */
    public ChargeurPlugin() {
    }

    /**
     * Constucteur initialisant le tableau de fichier à charger.
     * @param files Tableau de String contenant la liste des fichiers à charger.
     */
    public ChargeurPlugin(List<String> files) {
        this();
        this.files = files;
    }

    public void setFiles(List<String> files) {
        this.files = files;
    }

    public List<String> getFiles(){
        return files;
    }

    /**
     * Fonction de chargement de tout les plugins de type MediaPlugins
     * @return Une collection de MediaPlugins contenant les instances des plugins
     */
    public MediaPluginInterface[] loadAllMediaPlugins() throws Exception {
        //Chargement des plugin a instancier
        this.initializeLoader();
        
        if(classMediaPlugins == null){
            return new MediaPluginInterface[0];
        }
        MediaPluginInterface[] tmpPlugins = new MediaPluginInterface[this.classMediaPlugins.size()];

        //Pour chaque classe a instancier
        for (int index = 0; index < tmpPlugins.length; index++) {

            //On va créer une nouvelle instance de l'objet contenu dans la liste grâce à newInstance()
            //et on le cast en MediaPluginsInterface. Vu que la classe implémente MediaPluginsInterface, le cast est toujours correct
            tmpPlugins[index] = (MediaPluginInterface) ((Class) this.classMediaPlugins.get(index)).newInstance();
        }

        return tmpPlugins;
    }

    /**
     * Fonction de chargement de tout les plugins de type SearchPlugins
     * @return Un plugin RecherchePluginInterface instancié
     */
    public RecherchePluginInterface loadSearchPlugin() throws Exception {
        this.initializeLoader();
        if(classSearchPlugins == null){
            return null;
        }
        RecherchePluginInterface plugin = (RecherchePluginInterface) ((Class) this.classSearchPlugins).newInstance();
        return plugin;

    }

    /**
     * Parcourt le contenu du plugin passé en paramètre du loader et enregistre
     * les classes qui implémentent les interfaces des plugins afin que celles ci
     * soit instanciée.
     * @throws IllegalAccessException
     * @throws IllegalArgumentException
     * @throws InvocationTargetException
     */
    private void initializeLoader() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
        //On vérifie que la liste des plugins à charger à été initialisé
        if (this.files == null || this.files.isEmpty()) {
            return;
        }

        //Réinitialisation du contenu des plugins
        classMediaPlugins = new ArrayList();
        classSearchPlugins = null;

        File[] f = new File[this.files.size()];
        //Pour charger le .jar en memoire
        URLClassLoader loader;
        //Pour la comparaison de chaines
        String tmp = "";
        //Pour le contenu de l'archive jar
        Enumeration enumeration;
        //Pour déterminé quels sont les interfaces implémentées
        Class tmpClass = null;

        for (int index = 0; index < f.length; index++) {
            try {
                f[index] = new File(this.files.get(index));
                if (!f[index].exists()) {
                    break;
                }
                URL u = f[index].toURI().toURL();
                //On invoque la méthode addURL de l'URLClassLoader du systeme afin de
                //charger les classes contenues dans le fichier.jar
                loader = new URLClassLoader(new URL[]{u});
                Method method = URLClassLoader.class.getDeclaredMethod("addURL", PARAMETERS);
                method.setAccessible(true);
                method.invoke(URLClassLoader.getSystemClassLoader(), new Object[]{ u });
                //On charge le jar en mémoire
                JarFile jar = new JarFile(f[index].getAbsolutePath());
                //On récupére le contenu du jar
                enumeration = jar.entries();
                while (enumeration.hasMoreElements()) {
                    tmp = enumeration.nextElement().toString();
                    //On vérifie que le fichier courant est un .class (et pas un fichier d'informations du jar )
                    if (tmp.length() > 6 && tmp.substring(tmp.length() - 6).compareTo(".class") == 0) {
                        tmp = tmp.substring(0, tmp.length() - 6);
                        tmp = tmp.replaceAll("/", ".");
                        tmpClass = Class.forName(tmp, true, loader);
                        for (int i = 0; i < tmpClass.getInterfaces().length; i++) {
                            //Une classe ne doit pas appartenir à deux catégories de plugins différents.
                            //Si tel est le cas on ne la place que dans la catégorie de la première interface correct
                            // trouvée
                            if (tmpClass.getInterfaces()[i].getName().toString().equals("plugins.MediaPluginInterface")) {
                                this.classMediaPlugins.add(tmpClass);
                            } else if (tmpClass.getInterfaces()[i].getName().toString().equals("plugins.RecherchePluginInterface")) {
                                this.classSearchPlugins = tmpClass;
                            }
                        }
                    }
                }
            } catch (ClassNotFoundException ex) {
                Logger.getLogger(ChargeurPlugin.class.getName()).log(Level.SEVERE, null, ex);
            } catch (MalformedURLException ex) {
                Logger.getLogger(ChargeurPlugin.class.getName()).log(Level.SEVERE, null, ex);
            } catch (IOException ex) {
                Logger.getLogger(ChargeurPlugin.class.getName()).log(Level.SEVERE, null, ex);
            } catch (NoSuchMethodException ex){
                System.err.println("NoSuchMethodException");
            } catch (SecurityException ex){
                System.err.println("SecurityException");
            }
        }
    }
}
